
#include "fat.h"
#include "string.h"

typedef struct
{
    uint8_t  JumpInstruction[3];
    uint8_t  OEMInfo[8];
    uint16_t SectorSize;
    uint8_t  SectorsPerCluster;
    uint16_t ReservedSectors;
    uint8_t  FATCopies;
    uint16_t RootDirectoryEntries;
    uint16_t TotalSectors16;
    uint8_t  MediaDescriptor;
    uint16_t SectorsPerFAT;
    uint16_t SectorsPerTrack;
    uint16_t Heads;
    uint32_t HiddenSectors;
    uint32_t TotalSectors32;
    uint8_t  PhysicalDriveNum;
    uint8_t  Reserved;
    uint8_t  ExtendedBootSig;
    uint32_t VolumeSerialNumber;
    uint8_t  VolumeLabel[11];
    uint8_t  FilesystemIdentifier[8];
} __attribute__((packed)) FAT_BootBlock;

typedef struct
{
    char     name[8];
    char     ext[3];
    uint8_t  attrs;
    uint8_t  reserved;
    uint8_t  createTimeFine;
    uint16_t createTime;
    uint16_t createDate;
    uint16_t lastAccessDate;
    uint16_t highStartCluster;
    uint16_t updateTime;
    uint16_t updateDate;
    uint16_t startCluster;
    uint32_t size;
} __attribute__((packed)) DirEntry;

STATIC_ASSERT(sizeof(DirEntry) == 32);

struct TextFile
{
    const char  name[11];
    const char* content;
};

static const struct TextFile info[] =
{
    {.name = "Ready   txt", .content = NULL},
};

#define NUM_FILES             (sizeof(info) / sizeof(info[0]))
#define NUM_DIRENTRIES        (NUM_FILES + 1) // Code adds volume label as first root directory entry

#define RESERVED_SECTORS      1
#define ROOT_DIR_SECTORS      1
#define SECTORS_PER_FAT       1

#define START_FAT0            RESERVED_SECTORS
#define START_FAT1            (START_FAT0 + SECTORS_PER_FAT)
#define START_ROOTDIR         (START_FAT1 + SECTORS_PER_FAT)
#define START_CLUSTERS        (START_ROOTDIR + ROOT_DIR_SECTORS)

// all directory entries must fit in a single sector
// because otherwise current code overflows buffer
#define DIRENTRIES_PER_SECTOR (STORAGE_BLK_SIZ / sizeof(DirEntry))
STATIC_ASSERT(NUM_DIRENTRIES < DIRENTRIES_PER_SECTOR * ROOT_DIR_SECTORS);

static const FAT_BootBlock BootBlock =
{
    .JumpInstruction      = {0xeb, 0x3c, 0x90},
    .OEMInfo              = "MSDOS5.0",
    .SectorSize           = STORAGE_BLK_SIZ,
    .SectorsPerCluster    = 1,
    .ReservedSectors      = RESERVED_SECTORS,
    .FATCopies            = 2,
    .RootDirectoryEntries = (ROOT_DIR_SECTORS * DIRENTRIES_PER_SECTOR),
    .TotalSectors16       = STORAGE_BLK_NBR - 2,
    .MediaDescriptor      = 0xf0, // typical for unpartitioned disks
    .SectorsPerFAT        = SECTORS_PER_FAT,
    .SectorsPerTrack      = 1,
    .Heads                = 1,
    .HiddenSectors        = 0,
    .TotalSectors32       = 0,
    .PhysicalDriveNum     = 0x80, // to match MediaDescriptor of 0xf0
    .Reserved             = 0,
    .ExtendedBootSig      = 0x29,
    .VolumeSerialNumber   = 0x00420042,
    .VolumeLabel          = "KMAX Update",
    .FilesystemIdentifier = "FAT12   ",
};

static DirEntry     file_attr    = {0};
enum UPGRADE_STATUS msd_UpStatus = UPGRADE_READY;

__weak int8_t Write_Memory(uint32_t address, uint8_t* data, uint32_t len)
{
    (void)(address);
    (void)(data);
    (void)(len);
    return -1;
}

__weak int8_t Read_Memory(uint32_t address, uint8_t* data, uint32_t len)
{
    (void)(address);
    (void)(data);
    (void)(len);
    return -1;
}

void padded_memcpy(char* dst, const char* src, int len)
{
    for(int i = 0; i < len; ++i)
    {
        if(*src)
            *dst = *src++;
        else
            *dst = ' ';

        dst++;
    }
}

uint32_t FATReadLBA(uint32_t block_no, uint8_t* data, uint16_t len)
{
    uint16_t i;
    uint32_t sectionIdx = block_no;
    uint32_t read_size  = len * STORAGE_BLK_SIZ;
    memset(data, 0, read_size);

    LOG_INF("%s 0x%08x %d\n", __func__, block_no, len);

    if(block_no == 0)
    {
        /* Write Boot Sector info */
        memcpy(data, &BootBlock, sizeof(BootBlock));
        /* Boot Sector requires these 2 bytes at end */
        data[510] = 0x55;
        data[511] = 0xAA;
    }
    else if(block_no < START_ROOTDIR)
    {
        // Requested FAT table sector
        sectionIdx -= START_FAT0;

        if(sectionIdx >= SECTORS_PER_FAT)
            sectionIdx -= SECTORS_PER_FAT; // second FAT is same as the first...

        if(sectionIdx == 0)
        {
            data[0] = BootBlock.MediaDescriptor; // must match MediaDescriptor

            // WARNING -- code presumes only one NULL .content for .UF2 file
            //            and all non-NULL .content fit in one sector
            //            and requires it be the last element of the array
            uint16_t bytes_cnt = (NUM_FILES / 2) * 3;
            uint8_t  bits_cnt  = NUM_FILES % 2;

            for(i = 1; i < bytes_cnt; i++)
            {
                data[i] = 0xff;
            }

            if(0 != bits_cnt)
            {
                data[i++] = 0xff;
                data[i]   = 0x0f;
            }
        }
    }
    else if(block_no < START_CLUSTERS)
    {
        // Requested sector of the root directory
        sectionIdx -= START_ROOTDIR;

        if(sectionIdx == 0)
        {
            DirEntry* d = (void*)data;
            padded_memcpy(d->name, (const char*)BootBlock.VolumeLabel, 11);
            d->attrs = 0x28;

            for(i = 0; i < NUM_FILES; i++)
            {
                d++;
                const struct TextFile* inf = &info[i];
                d->size                    = inf->content ? strlen(inf->content) : 0;
                d->startCluster            = i + 2;
                padded_memcpy(d->name, inf->name, 11);
                d->createDate = 0x4d99;
                d->updateDate = 0x4d99;
            }
        }
    }
    else
    {
        // Requested sector from file space
        sectionIdx -= START_CLUSTERS;

        // WARNING -- code presumes all but last file take exactly one sector
        uint32_t offset = sectionIdx * STORAGE_BLK_SIZ;
        Read_Memory(offset, data, read_size);
    }

    return read_size;
}

uint32_t FATWriteLBA(uint32_t block_no, uint8_t* data, uint16_t len)
{
    uint32_t i;
    uint32_t sectionIdx = block_no;
    uint32_t write_size = len * STORAGE_BLK_SIZ;

    LOG_INF("%s 0x%08x %d\n", __func__, block_no, len);

    if(block_no < START_CLUSTERS)
    {
        sectionIdx -= START_ROOTDIR;

        if(sectionIdx == 0)
        {
            DirEntry* d = (void*)data;
            d++; // Skip Root Dir
            d++; // Skip Status File

            for(i = 2; i < BootBlock.RootDirectoryEntries; i++)
            {
                if((d->attrs == 0x20) && ((d + 1)->attrs == 0x00))
                    break;
                else
                    d++;
            }

            if(i < BootBlock.RootDirectoryEntries)
            {
                memcpy(&file_attr, d, sizeof(DirEntry));
            }
            else
            {
                memset(&file_attr, (int)0, sizeof(DirEntry));
            }
        }
    }
    else
    {
        sectionIdx -= START_CLUSTERS;

        if(sectionIdx < 3)
        {
            msd_UpStatus = UPGRADE_UNKNOWN;
        }
        else if((!memcmp(&(file_attr.name[8]), "BIN", 3) || !memcmp(&(file_attr.name[8]), "bin", 3)))
        {
            if(msd_UpStatus == UPGREDE_DONE || msd_UpStatus == UPGRADE_SUCCESS)
                return 0;

            uint32_t file_size = file_attr.size;
            uint32_t offset    = (sectionIdx - 3) * STORAGE_BLK_SIZ;
            int8_t   WStatus   = 0;

            if((offset + write_size) > file_size)
            {
                WStatus = Write_Memory(offset, data, (file_size - offset));
            }
            else
            {
                WStatus = Write_Memory(offset, data, write_size);
            }

            if(WStatus == 0)
            {
                /*Write data finish*/
                if((offset + write_size) >= file_size)
                {
                    msd_UpStatus = UPGRADE_SUCCESS;
                }
            }
            else
            {
                msd_UpStatus = UPGRADE_FAILED;
            }
        }
        else
        {
            msd_UpStatus = UPGRADE_UNKNOWN;
        }
    }

    return write_size;
}

uint32_t FATSetStatusFileName(const char* name)
{
    uint8_t i;
    uint8_t len = (uint8_t)strlen(name);

    for(i = 0; i < 8 && i < len; i++)
    {
        file_attr.name[i] = name[i];
    }

    for(; i < 8; i++)
    {
        file_attr.name[i] = ' ';
    }

    return i;
}
